using System;
using System.Globalization;

namespace remes.BLoc.CLU
{
   internal class Option
   {
      private String m_strName;
      private String m_strValue;

      public Option(String strName, String strValue)
      {
         m_strName = strName;
         m_strValue = strValue;
      }

      public String Name { get { return m_strName; } }
      public String Value { get { return m_strValue; } }
   }

   internal class Abbrevs
   {
      private String[] m_aOptions;
      private bool[] m_bRequiresValue;
      private bool[] m_bCanHaveValue;

      public Abbrevs(String[] aOptions)
      {
         m_aOptions = new String[aOptions.Length];
         m_bRequiresValue = new bool[aOptions.Length];
         m_bCanHaveValue = new bool[aOptions.Length];

         // Store option list in lower case for canonical comparison.
         for (int i = 0; i < aOptions.Length; i++)
         {
            String strOption = aOptions[i].ToLower(CultureInfo.InvariantCulture);

            // A leading '*' implies the option requires a value
            // (the '*' itself is not stored in the option name).
            if (strOption.StartsWith("*"))
            {
               m_bRequiresValue[i] = true;
               m_bCanHaveValue[i] = true;
               strOption = strOption.Substring(1);
            }
            else if (strOption.StartsWith("+"))
            {
               m_bRequiresValue[i] = false;
               m_bCanHaveValue[i] = true;
               strOption = strOption.Substring(1);
            }

            m_aOptions[i] = strOption;
         }
      }

      public String Lookup(String strOpt, out bool bRequiresValue, out bool bCanHaveValue)
      {
         String strOptLower = strOpt.ToLower(CultureInfo.InvariantCulture);
         int i;
         bool bMatched = false;
         int iMatch = -1;

         // Compare option to stored list.
         for (i = 0; i < m_aOptions.Length; i++)
         {
            // Exact matches always cause immediate termination of
            // the search
            if (strOptLower.Equals(m_aOptions[i]))
            {
               bRequiresValue = m_bRequiresValue[i];
               bCanHaveValue = m_bCanHaveValue[i];
               return m_aOptions[i];
            }

            // Check for potential match (the input word is a prefix
            // of the current stored option).
            if (m_aOptions[i].StartsWith(strOptLower))
            {
               // If we've already seen a prefix match then the
               // input word is ambiguous.
               if (bMatched)
                  throw new ArgumentException(String.Format("Ambigous command line option: '{0}'", strOpt));

               // Remember this partial match.
               bMatched = true;
               iMatch = i;
            }
         }

         // If we get here with bMatched set, we saw one and only one
         // partial match, so we've got a winner.
         if (bMatched)
         {
            bRequiresValue = m_bRequiresValue[iMatch];
            bCanHaveValue = m_bCanHaveValue[iMatch];
            return m_aOptions[iMatch];
         }

         // Else the word doesn't match at all.
         throw new ArgumentException(String.Format("Unknown command line option: '{0}'", strOpt));
      }
   }

   internal class CommandLine
   {
      private String[] m_aArgList;
      private Option[] m_aOptList;
      private int m_iArgCursor;
      private int m_iOptCursor;
      private Abbrevs m_sValidOptions;

      public CommandLine(String[] aArgs, String[] aValidOpts)
      {
         int i, iArg, iOpt;

         // Keep a list of valid option names.
         m_sValidOptions = new Abbrevs(aValidOpts);

         // Temporary lists of raw arguments and options and their
         // associated values.
         String[] aArgList = new String[aArgs.Length];
         Option[] aOptList = new Option[aArgs.Length];

         // Reset counters of raw arguments and option/value pairs found
         // so far.
         iArg = 0;
         iOpt = 0;

         // Iterate through words of command line.
         for (i = 0; i < aArgs.Length; i++)
         {
            // Check for option or raw argument.
            if (aArgs[i].StartsWith("/") ||
                aArgs[i].StartsWith("-"))
            {
               String strOpt;
               String strVal = null;
               bool bRequiresValue;
               bool bCanHaveValue;

               // It's an option. Strip leading '/' or '-' and
               // anything after a value separator (':' or
               // '=').
               int iColon = aArgs[i].IndexOfAny(new char[] { ':', '=' });
               if (iColon == -1)
                  strOpt = aArgs[i].Substring(1);
               else
                  strOpt = aArgs[i].Substring(1, iColon - 1);

               // Look it up in the table of valid options (to
               // check it exists, get the full option name and
               // to see if an associated value is expected).
               strOpt = m_sValidOptions.Lookup(strOpt, out bRequiresValue, out bCanHaveValue);

               // Check that the user hasn't specified a value separator for an option 
               // that doesn't take a value.
               if (!bCanHaveValue && (iColon != -1))
                  throw new ApplicationException(String.Format("Command line option '{0}' does not require an value", strOpt));

               // Check that the user has put a colon if the option requires a value.
               if (bRequiresValue && (iColon == -1))
                  throw new ApplicationException(String.Format("Command line option '{0}' requires an value", strOpt));

               // Go look for a value if there is one.
               if (bCanHaveValue && iColon != -1)
               {
                  if (iColon == (aArgs[i].Length - 1))
                  {
                     // No value separator, or
                     // separator is at end of
                     // option; look for value in
                     // next command line arg.
                     if (i + 1 == aArgs.Length)
                     {
                        throw new ApplicationException(String.Format("Command line option '{0}' requires an value", strOpt));
                     }
                     else
                     {
                        if ((aArgs[i + 1].StartsWith("/") || aArgs[i + 1].StartsWith("-")))
                           throw new ApplicationException(String.Format("Command line option '{0}' requires an value", strOpt));

                        strVal = aArgs[i + 1];
                        i++;
                     }
                  }
                  else
                  {
                     // Value is in same command line
                     // arg as the option, substring
                     // it out.
                     strVal = aArgs[i].Substring(iColon + 1);
                  }
               }

               // Build the option value pair.
               aOptList[iOpt++] = new Option(strOpt, strVal);
            }
            else
            {
               // Command line word is a raw argument.
               aArgList[iArg++] = aArgs[i];
            }
         }

         // Allocate the non-temporary arg and option lists at exactly
         // the right size.
         m_aArgList = new String[iArg];
         m_aOptList = new Option[iOpt];

         // Copy in the values we've calculated.
         Array.Copy(aArgList, m_aArgList, iArg);
         Array.Copy(aOptList, m_aOptList, iOpt);
      }

      public int NumArgs { get { return m_aArgList.Length; } }

      public int NumOpts { get { return m_aOptList.Length; } }

      public String GetNextArg()
      {
         if (m_iArgCursor >= m_aArgList.Length)
            return null;
         return m_aArgList[m_iArgCursor++];
      }

      public Option GetNextOption()
      {
         if (m_iOptCursor >= m_aOptList.Length)
            return null;
         return m_aOptList[m_iOptCursor++];
      }
   }
}